//--------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/boot.h>
#include <avr/interrupt.h>

#include "dkbasic.h"
#include "dkfile_flash.h"
#include "dkstring.h"


//--------------------------------------------------------------------

struct _dkdeviceio flashdeviceio =
{
	dkflash_open, 
	dkflash_close, 
	dkflash_read_byte, 
	dkflash_write_byte, 
	dkflash_read_bytes, 
	dkflash_write_bytes, 
	dkflash_seek, 
	dkflash_pos,
	dkflash_eof,
	dkflash_error
};


//--------------------------------------------------------------------

struct _dk_flash dkflash;


//--------------------------------------------------------------------
int dkflash_open( DKFILE *stream )
{
	char t[MAX_TOKEN_LENGTH+1];					// used for token parsing with dkgettoken()
	char *filename;
//	int16_t i;
//	uint32_t len;


	filename = stream->filename;

	filename += 3;								// skip past "FLA"


	filename = dkgettoken( filename, t );		// get : as token
		
	if( (*t) != ':' )
		return 1;								// error in filename! (or filename done?)


// filename points past : in filename string

//	filename = dkgettoken( filename, t );		// get token following :
//	if( !t[0] )
//		return 1;								// error in filename! (or filename done?)

		
	stream->info = &dkflash;
	dkflash.ptr = DKFLASH_FILESTART;
	dkflash.start = DKFLASH_FILESTART;
	dkflash.len = FLASHEND-DKFLASH_FILESTART;
/*
	while( !dkfeof(stream) && !dkferror(stream) )
	{		
		for( i=0; i<MAX_TOKEN_LENGTH; i++ )
		{
			t[i] = dkflash_read_byte( &dkflash );
			if( t[i] == 0xFF )
				break;							// looks like an erased byte
			if( !t[i] )
				break;							// stop reading when NULL is reached
		}
		if( i<MAX_TOKEN_LENGTH && t[i]==0xFF )
			;									// FF was read, probably erased flash
		t[i]='\0';								// make sure string is NULL-terminated
		
		
		len = pgm_read_dword_far( dkflash.ptr );	// read length from flash
		dkflash.ptr+=4;
		
		if( !strcmp( t, filename ) )
		{										// found matching filename
			dkflash.len = len;
			dkflash.start = dkflash_pos( &dkflash );	// set start = current position
			return 0;
		}
		
		dkflash.ptr += len;
	}
*/
// file not found

	
	
	return 0;
}

//--------------------------------------------------------------------
int dkflash_close( void *deviceinfo )
{
	return 0;
}

//--------------------------------------------------------------------
int dkflash_read_byte(void *deviceinfo)
{
	if( dkflash_eof(deviceinfo) || dkflash_error(deviceinfo) )
		return -1;
		
	return pgm_read_byte_far( ((struct _dk_flash *)deviceinfo)->ptr++ );
}
//--------------------------------------------------------------------
int dkflash_write_byte(void *deviceinfo, int c)
{
//	pgm_write_byte( (void *)((struct _dk_flash *)deviceinfo)->ptr, c );
	dkflash_write_bytes(deviceinfo, 1, &c );
	return c;
}
//--------------------------------------------------------------------
int dkflash_read_bytes(void *deviceinfo, int n, void *ptr)
{
	int i;
	
	for( i=0; i<n && !dkflash_eof(deviceinfo) && !dkflash_error(deviceinfo); i++ )
		((uint8_t *)ptr)[i] = dkflash_read_byte( deviceinfo );

	return  n;
}
//--------------------------------------------------------------------

// TODO:  THIS SHOULDN'T BE NECESSARY!  WTF
void BOOTLOADER_SECTION dummy(void) 
{
	void (*reset)(void);
	
	reset = 0;
	
	reset();
}

//void boot_program_page (uint32_t page, uint8_t *buf) 
void BOOTLOADER_SECTION boot_program_page (uint32_t page, uint8_t *buf) 
{
    uint16_t i;
    uint8_t sreg;

    // Disable interrupts.

    sreg = SREG;
    cli();
    
    eeprom_busy_wait ();

    boot_page_erase (page);
    boot_spm_busy_wait ();      // Wait until the memory is erased.

    for (i=0; i<SPM_PAGESIZE; i+=2)
    {
        // Set up little-endian word.

        uint16_t w = *buf++;
        w += (*buf++) << 8;
        
        boot_page_fill (page + i, w);
    }

    boot_page_write (page);     // Store buffer in flash page.
    boot_spm_busy_wait();       // Wait until the memory is written.

    // Reenable RWW-section again. We need this if we want to jump back
    // to the application after bootloading.

    boot_rww_enable ();

    // Re-enable interrupts (if they were ever enabled).

    SREG = sreg;
}
//--------------------------------------------------------------------
int dkflash_write_bytes(void *deviceinfo, int n, void *ptr )
{
//	flash_write_block( ptr, (void *)((struct _dk_flash *)deviceinfo)->ptr, n );
//	((struct _dk_flash *)deviceinfo)->ptr += n;

	uint32_t PageStart;
	uint32_t FullLength;
	uint32_t i;
	uint8_t FullData[0x100];


	PageStart = ((struct _dk_flash *)deviceinfo)->ptr & 0xFFFFFF00;
	FullLength = (((struct _dk_flash *)deviceinfo)->ptr+n) - PageStart;
	FullLength+=0xFF;
	FullLength&=0xFFFFFF00;		// make fulllength a multiple of pages  (page size = 256 bytes!)

	if( PageStart==((struct _dk_flash *)deviceinfo)->ptr && FullLength==n)		// we have been passed a proper page, just write it and return
	{
		boot_program_page( ((struct _dk_flash *)deviceinfo)->ptr, ptr );
		((struct _dk_flash *)deviceinfo)->ptr += n;
		return n;
	}

	for( i=0; PageStart+i<((struct _dk_flash *)deviceinfo)->ptr; i++ )		// fill in page data from before address 
		FullData[i] = pgm_read_byte_far(PageStart+i);	// (because we were passed address not on page boundary)

	for( i=((struct _dk_flash *)deviceinfo)->ptr-PageStart; i<FullLength; i++)
	{
		if( i && !(i%0x100) )
			boot_program_page( PageStart+i-0x100, FullData );

		if( PageStart+i <((struct _dk_flash *)deviceinfo)->ptr+n )
			FullData[i&0xFF] = ((uint8_t *)ptr)[i-(((struct _dk_flash *)deviceinfo)->ptr-PageStart)];
		else
			FullData[i&0xFF] = pgm_read_byte_far(PageStart+i);
	}

	boot_program_page( PageStart, FullData );

	((struct _dk_flash *)deviceinfo)->ptr += n;



	return  n;
}
//--------------------------------------------------------------------
int dkflash_seek(void *deviceinfo, off_t offset, int whence)
{
	switch( whence )
	{
		case SEEK_SET:
			break;
	}
	
	return 0;
}
//--------------------------------------------------------------------
fpos_t dkflash_pos(void *deviceinfo)
{
	return ((struct _dk_flash *)deviceinfo)->ptr;
}
//--------------------------------------------------------------------
int dkflash_eof(void *deviceinfo)
{
	if( ((struct _dk_flash *)deviceinfo)->ptr > ( ((struct _dk_flash *)deviceinfo)->start+((struct _dk_flash *)deviceinfo)->len - 1 ) || ((struct _dk_flash *)deviceinfo)->ptr >= FLASHEND )
		return -1;

	return 0;
}
//--------------------------------------------------------------------
int dkflash_error(void *deviceinfo)
{
	return 0;
}

//--------------------------------------------------------------------

